package lab_2_server.handlers;



import ProtocolXML.*;
import ProtocolXML.NotCompatibilityTypeException;
import java.io.IOException;
import java.util.Set;
import lab_2_server.MyLogger;
import lab_2_server.Server;
import lab_2_server.ServerID;


/**
 * Handler for connected clients
 * @author pacific
 */
public class ClientHandler extends AbstractHandler implements Runnable {

    public ClientHandler(Server newServer) {
        setServer(newServer) ;
    }

    @Override
    public void run() {
        //Work allways, when thread not interrupted
        while (true) {
            //Go to sleep, if Socket == null
            if(getSocket() == null) {
                synchronized (this) {
                    try {
                        wait();
                    } catch (InterruptedException ex) {
                        MyLogger.putMessange("Not can sleep. " + ex);
                        return ;
                    }
                }
            }
            MyLogger.putMessange("Handler wake upped.") ;

            //Init fields: InputStream, OutputStream
            try {
                initFields();
            } catch (IOException ex) {
                MyLogger.putMessange("Exception init fields. " + ex);
                return;
            }

            //In cycle read and process message
            Message msg = null ;
            while(getSocket() != null) {
                try {
                    MyLogger.putMessange("Read message from client...") ;
                    msg = Message.read(getIStream());
                    MyLogger.putMessange("Message getted.") ;
                } catch (Exception ex) {
                    MyLogger.putMessange("Exception read from stream. " + ex);
                    disconnect() ;
                    continue;
                }
                process(msg);
            }
        }
    }


    /**
     * Every time, when connect new user initialization
     * fields: InputStream, OutputStream from Socket
     */
    private void initFields() throws IOException {
        if(getIStream()==null) {
            setIStream(getSocket().getInputStream());
        }
        if(getOStream()==null) {
            setOStream(getSocket().getOutputStream());
        }
    }


    /**
     * Disconnect client from server
     */
    private void disconnect() {
        setSocket( null ) ;
        setIStream( null ) ;
        setOStream( null ) ;
        setNickNameUser( null ) ;

        
        getServer().getMapIdToHandler().remove(getUserID()) ;

        //moveToPull ;
        getServer().getClientsHandlers().remove(this) ;
        getServer().getHandlersPull().add( this );

        //Notify all users, server about disconnect
        Message disconnectedMsg = new Message() ;
        disconnectedMsg.msgIsUserDisconnect( getUserID() ) ;
        for( ClientHandler currentHandler : getServer().getClientsHandlers() ) {
            currentHandler.writeMessage( disconnectedMsg ) ;
        }
        for( ServerHandler currentHandler : getServer().getServerHandlers() ) {
            currentHandler.writeMessage( disconnectedMsg ) ;
        }

        //Onli this sequence
        getServer().getMonitor().delClient( this );
        setMyStringID( null ) ;
    }

    
    


    /**
     * Process received message
     *
     * @param msg - message for process
     */
    private void process( Message msg ) {
        TypeOfMsg typeOfMsg = msg.getTypeOfMsg() ;
        try {
            switch(typeOfMsg) {
                //This is first message, which send client to server
                case AUTORIZATION : {
                    MyLogger.putMessange( "Give message 'AUTORIZATION'" ) ;
                    String login = msg.getLogin() ;
                    String password  = msg.getPassword() ;
                    
                    //! FIXME Add autorization

                    setUserID( getServer().getNewId() ) ;
                    setNickNameUser( login ) ;

                    Message msgWithUserConnect = new Message() ;
                    //Send to user more message with "user connect"
                    Set<Integer> ids = getServer().getMapIdToHandler().keySet() ;
                    for( int idOfUser : ids) {
                        AbstractHandler handler = getServer().getMapIdToHandler().get(idOfUser) ;
                        msgWithUserConnect.msgIsUserConnected(idOfUser, handler.getNickname(idOfUser));
                        writeMessage( msgWithUserConnect );
                    }
                    MyLogger.putMessange("Messages with user sendet to client.") ;
                    
                    //Send to user "List with servers"
                    Message msgServerID = new Message() ;
                    for( ServerID serverID : getServer().getServersID() ) {
                        msgServerID.msgIsServerID( serverID );
                        writeMessage( msgServerID ) ;
                    }
                    MyLogger.putMessange("Messages with ServerID sended.") ;
                    
                    Message notifyMsg = new Message() ;
                    notifyMsg.msgIsUserConnected( getUserID(), getNickname(getUserID()) );

                    for( ClientHandler client : getServer().getClientsHandlers() ) {
                        if(client == this)
                            continue ;
                        client.writeMessage(notifyMsg);
                    }
                    //Notify other servers about connect new user
                    for( ServerHandler server : getServer().getServerHandlers() ) {
                        server.writeMessage(notifyMsg) ;
                        MyLogger.putMessange("Notify " + server + " server About connect") ;
                    }

                    getServer().getMapIdToHandler().put( getUserID(), this ) ;
                    getServer().getMonitor().addClient( this );
                    break ;
                }

                //This message client send before disconnect
                case DISCONNECT : {
                    MyLogger.putMessange( "Give message 'DISCONNECT'" ) ;

                    disconnect();
                    break ;
                }
                //User send message other user(group chat)
                case SEND_MESSAGE_USR : {
                    MyLogger.putMessange("Give message 'SEND_MESSAGE_USR'") ;
                    int receiverID = msg.getIdReceiver() ;
                    String message = msg.getMsg() ;

                    //Create message with "sender" info
                    Message msgToHandler = new Message() ;
                    msgToHandler.msgIsSendingToSrv( receiverID, getUserID(), message ) ;

                    //Find handler for with "Receiver"
                    AbstractHandler handlerRecipient = getServer().getMapIdToHandler().get( receiverID ) ;

                    //Write message to handler
                    handlerRecipient.writeMessage( msgToHandler ) ;
                    
                    break ;
                }
                //User create chat group
                case CREATE_GROUP : {
                    MyLogger.putMessange( "Give message 'CREATE_GROUP'" ) ;
                    //! FIXME Group chat (ClientHandler)
                    break ;
                }
                //User invite other user to created chat group
                case INV_USER_TO_GROUP : {
                    MyLogger.putMessange( "Give message 'INV_USER_TO_GROUP'" ) ;
                    //! FIXME Group chat (ClientHandler)
                    break ;
                }
                case SERVER_INF : {
                    ServerHandler serverHandler =
                            new ServerHandler( getServer(), getSocket(), getIStream(), getOStream() ) ;
                    Thread thread = new Thread( serverHandler ) ;

                    getServer().getClientsHandlers().remove( this ) ;
                    getServer().getHandlersPull().add( this ) ;
                    setSocket( null ) ;
                    thread.start() ;
                    break ;
                }
                //If getted SEND_MESSAGE_SRV,SERVER_LIST, USERS_LIST, USER_CONNECT, his not suported from Client
                default : {
                    MyLogger.putMessange( "Give message SEND_MESSAGE_SRV or SERVER_LIST or USERS_LIST or USER_CONNECT" ) ;
                }
            }
        }//switch(typeOfMsg)
        //Catched, if in switch use method not compatibility with type of message
        catch( NotCompatibilityTypeException ex ) {
            MyLogger.putMessange( "Message not match for method. " + ex );
        }
    }


    /**
     * Return formated string with information about client
     * @return String in view "NickName(UserID:SocketAddress)"
     */
    @Override
    public String toString() {
        if ( getMyStringID() == null ) {
            StringBuilder builder = new StringBuilder();
            builder.append(getNickname(0));
            builder.append("(");
            builder.append(getUserID());
            builder.append(":");
            builder.append(getSocket().getRemoteSocketAddress());
            builder.append(")");
            setMyStringID( builder.toString() ) ;
        }
        return getMyStringID() ;
    }

    
    //----------Fields------------------

    //ID user, when wich processec handler
    private int userID ;
    //NickName user wich processec handler
    private String nickNameUser ;

    private String myStringID ;


    //--------GETTERS AND SETTERS--------

    //For "User ID"
    public int getUserID() {
        return userID ;
    }
    private void setUserID( int userID ) {
        this.userID = userID ;
    }
    //For "Nick Name"
    public String getNickname( int idOfUser ) {
        return nickNameUser ;
    }
    private void setNickNameUser( String nickNameUser ) {
        this.nickNameUser = nickNameUser ;
    }

    private String getMyStringID() {
        return myStringID;
    }

    private void setMyStringID(String myStringID) {
        this.myStringID = myStringID;
    }
}
